package ua.naiksoftware.stomp.provider;

import io.reactivex.annotations.NonNull;
import io.reactivex.annotations.Nullable;
import ohos.hiviewdfx.HiLog;
import ohos.hiviewdfx.HiLogLabel;
import org.java_websocket.WebSocket;
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.drafts.Draft_6455;
import org.java_websocket.exceptions.InvalidDataException;
import org.java_websocket.handshake.ClientHandshake;
import org.java_websocket.handshake.ServerHandshake;
import ua.naiksoftware.stomp.dto.LifecycleEvent;

import java.net.URI;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeMap;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;

/**
 * Created by naik on 05.05.16.
 *
 * @since 2021-04-21
 */
public class WebSocketsConnectionProvider extends AbstractConnectionProvider {
    static final HiLogLabel LABEL = new HiLogLabel(HiLog.LOG_APP, 0x00201, "AbstractConnectionProvider");

    private static final String TAG = WebSocketsConnectionProvider.class.getSimpleName();

    private final String mUri;
    @NonNull
    private final Map<String, String> mConnectHttpHeaders;

    private WebSocketClient mWebSocketClient;
    private boolean isisHaveConnection;
    private TreeMap<String, String> mServerHandshakeHeaders;

    /**
     * Support UIR scheme ws://host:port/path
     *
     * @param connectHttpHeaders may be null
     * @param uri                uri
     */
    public WebSocketsConnectionProvider(String uri, @Nullable Map<String, String> connectHttpHeaders) {
        mUri = uri;
        mConnectHttpHeaders = connectHttpHeaders != null ? connectHttpHeaders : new HashMap<>();
    }

    /**
     * rawDisconnect
     */
    @Override
    public void rawDisconnect() {
        try {
            mWebSocketClient.closeBlocking();
        } catch (InterruptedException e) {
            HiLog.error(LABEL, "Thread interrupted while waiting for Websocket closing: ", e);
            throw new RuntimeException(e);
        }
    }

    /**
     * createWebSocketConnection
     */
    @Override
    protected void createWebSocketConnection() {
        if (isisHaveConnection) {
            throw new IllegalStateException("Already have connection to web socket");
        }
        mWebSocketClient = new WebSocketClient(URI.create(mUri), new Draft_6455(), mConnectHttpHeaders, 0) {
            @Override
            public void onWebsocketHandshakeReceivedAsClient(WebSocket conn, ClientHandshake request, @NonNull ServerHandshake response) throws InvalidDataException { webtodo(response); }
            @Override
            public void onOpen(@NonNull ServerHandshake handshakeData) {
                open(handshakeData);
            }
            @Override
            public void onMessage(String message) {
                emitMessage(message);
            }
            @Override
            public void onClose(int code, String reason, boolean remote) {
                closeAny(code, reason, remote);
            }
            @Override
            public void onError(Exception ex) {
                emitLifecycleEvent(new LifecycleEvent(LifecycleEvent.Type.ERROR, ex));
            }
        };


        if (mUri.startsWith("wss")) {
            try {
                SSLContext sc = SSLContext.getInstance("TLS");
                sc.init(null, null, null);
                SSLSocketFactory factory = sc.getSocketFactory();
                mWebSocketClient.setSocket(factory.createSocket());
            } catch (Exception e) {
                String message = e.getMessage();
                HiLog.error(LABEL, message);
            }
        }
        mWebSocketClient.connect();
        isisHaveConnection = true;
    }

    private void closeAny(int code, String reason, boolean remote) {
        HiLog.error(LABEL, "onClose: code=" + code + " reason=" + reason + " remote=" + remote);
        isisHaveConnection = false;
        emitLifecycleEvent(new LifecycleEvent(LifecycleEvent.Type.CLOSED));

        HiLog.error(LABEL, "Disconnect after close.");
        disconnect();
    }

    private void open(ServerHandshake handshakeData) {
        LifecycleEvent openEvent = new LifecycleEvent(LifecycleEvent.Type.OPENED);
        openEvent.setHandshakeResponseHeaders(mServerHandshakeHeaders);
        emitLifecycleEvent(openEvent);
    }

    private void webtodo(ServerHandshake response) {
        mServerHandshakeHeaders = new TreeMap<>();
        Iterator<String> keys = response.iterateHttpFields();
        while (keys.hasNext()) {
            String key = keys.next();
            mServerHandshakeHeaders.put(key, response.getFieldValue(key));
        }
    }

    /**
     * rawSend
     *
     * @param stompMessage message to send
     */
    @Override
    protected void rawSend(String stompMessage) {
        mWebSocketClient.send(stompMessage);
    }

    /**
     * getSocket
     *
     * @return Object
     */
    @Override
    protected Object getSocket() {
        return mWebSocketClient;
    }
}
